Skip to content

bugfix(smudge): Fix Microwave Heat Haze blackout on forced AA#2374

Merged
xezon merged 7 commits intoTheSuperHackers:mainfrom
githubawn:fix/nvidia-microwave-aa
Mar 14, 2026
Merged

bugfix(smudge): Fix Microwave Heat Haze blackout on forced AA#2374
xezon merged 7 commits intoTheSuperHackers:mainfrom
githubawn:fix/nvidia-microwave-aa

Conversation

@githubawn
Copy link

This PR aims to resolve the black screen bug caused by driver-forced MSAA, continuing and refining the work started in #1073.

The Bug: When users force MSAA via their driver control panel, the driver secretly upgrades the depth buffer to be multisampled. However, the Direct3D 8 API still reports MultiSampleType=NONE for created textures. When ScreenDefaultFilter attempts to use Render-To-Texture (RTT), it binds a non-MSAA texture to this secretly-MSAA depth buffer. This surface mismatch is a D3D API violation that silently breaks depth testing, resulting in a black screen.

This PR fixes the issue by permanently disabling the RTT path inside ScreenDefaultFilter::preRender (falling back to the CopyRects smudge path).

Co-authored-by: stm <14291421+stephanmeesters@users.noreply.github.com>
@greptile-apps
Copy link

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR fixes a black-screen regression triggered when users force MSAA through their GPU driver control panel. Because D3D8's D3DSURFACE_DESC can report MultiSampleType = NONE even when the driver has secretly upgraded the depth buffer to be multisampled, the original RTT path in ScreenDefaultFilter::preRender would bind a non-MSAA render texture against an MSAA depth buffer — an API violation that silently corrupts depth testing and produces a black screen.

Key changes:

  • ScreenDefaultFilter::preRender unconditionally returns FALSE, permanently bypassing the RTT redirection path for the default filter and leaving scene rendering on the normal back buffer.
  • W3DShaderManager::init now reads D3DSURFACE_DESC::MultiSampleType from the current render target and skips RTT resource creation entirely when MSAA is detected.
  • W3DShaderManager::startRenderToTexture adds a defense-in-depth failsafe: if SetRenderTarget fails at runtime (covering cases where MSAA detection in init was insufficient), it permanently releases all RTT resources so no subsequent filter can accidentally trigger the same mismatch.
  • W3DSmudgeManager removes all #ifdef USE_COPY_RECTS guards, making the CopyRects back-buffer copy path unconditional. testHardwareSupport now correctly reports SMUDGE_SUPPORT_YES via m_backgroundTexture when global RTT is disabled.
  • The SAFE_RELEASE macro is promoted from a local #define in W3DWater.cpp to WWCommon.h with a proper #ifndef redefinition guard.

One note: the permanent RTT disable in startRenderToTexture — while an important safety net — would also silence other RTT-dependent filter effects (BW filter, cross-fade, motion blur) if hit at runtime. Those effects have no CopyRects fallback and would simply not render. This is the correct trade-off (no crash, no corruption), but is worth being aware of.

Confidence Score: 4/5

  • Safe to merge; the core bug fix is sound, resource lifetimes are correctly managed, and the CopyRects fallback path is well-guarded.
  • The MSAA detection and permanent-disable strategy are architecturally correct and the resource management (SAFE_RELEASE, null-checks, ref-counted surfaces) is handled carefully across all return paths. The only notable concern is that ScreenDefaultFilter::postRender is now unreachable dead code that still asserts on the RTT texture, which could mislead future maintainers. Score is 4 rather than 5 because of that orphaned function body and because the broad permanent-disable in startRenderToTexture silently affects all other RTT-based effects if triggered at runtime.
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp — specifically the now-unreachable ScreenDefaultFilter::postRender body (lines 183–187).

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp Core of the fix: preRender now permanently returns FALSE (disabling RTT for the default filter), MSAA detection added to init(), and startRenderToTexture gains a permanent-disable failsafe on SetRenderTarget failure; one minor concern around m_oldRenderSurface being released in the permanent-disable path of startRenderToTexture while m_currentFilter may be stale, but endRenderToTexture is correctly guarded by m_renderingToTexture.
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp Removes USE_COPY_RECTS conditional compilation, unconditionally uses the CopyRects path; testHardwareSupport now reports SMUDGE_SUPPORT_YES when RTT is disabled globally if m_backgroundTexture is valid; render() acquires backBuffer and background at the top with proper null-check early returns; resource lifetimes appear correct across all return paths.
Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h m_backgroundTexture member unconditionally declared (USE_COPY_RECTS guard removed), consistent with making the CopyRects path always active.
Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp Local SAFE_RELEASE macro definition removed as it is now centralised in WWCommon.h.
Core/Libraries/Source/WWVegas/WWLib/WWCommon.h SAFE_RELEASE macro promoted to this shared header with a proper #ifndef guard to avoid redefinition; uses nullptr correctly.

Sequence Diagram

sequenceDiagram
    participant V as W3DView
    participant F as ScreenDefaultFilter
    participant S as W3DSmudgeManager
    participant D as D3D8 Device

    Note over V,D: OLD PATH - RTT broken under forced MSAA
    V->>F: filterPreRender
    F->>D: startRenderToTexture - SetRenderTarget with MSAA depth
    Note over D: Silent depth corruption, black screen
    V->>D: doRender into RTT texture
    V->>F: filterPostRender - endRenderToTexture, blit fullscreen
    V->>S: render smudges over blit

    Note over V,D: NEW PATH - CopyRects always safe
    V->>F: filterPreRender
    F-->>V: return FALSE, RTT never started
    V->>D: doRender directly to back buffer
    Note over V,F: filterPostRender is NOT called
    V->>S: render
    S->>S: testHardwareSupport - SMUDGE_SUPPORT_YES via m_backgroundTexture
    S->>D: Get back buffer surface
    S->>S: background Copy from back buffer
    S->>D: draw smudge quads using copied background texture
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp
Line: 183-187

Comment:
**`postRender` is now permanently dead code**

`ScreenDefaultFilter::preRender` always returns `FALSE`, so `W3DView.cpp` gates `filterPostRender` behind `if (preRenderResult)` and this function is never invoked. Despite that, it still calls `W3DShaderManager::endRenderToTexture()` and asserts on the result. Since it can never be reached through the normal filter dispatch, the assertion will never fire — but the function body is now misleading for future maintainers, and it references the RTT texture that is no longer populated for this filter. Consider either removing the body (returning `false` immediately with a comment) or removing the function entirely if no derived class relies on it.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: f7ffb6a

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@stephanmeesters
Copy link

Maybe we can accurately tell if MSAA was forced with some of the changes in #1073 like below, and use that in place of desc.MultiSampleType

static D3DFORMAT _Get_D3D_Back_Buffer_Format() { return DisplayFormat; }
...
WWINLINE unsigned int DX8Wrapper::Bytes_Per_Pixel(D3DFORMAT format)
{
	switch (format)
	{
	// 8-bit formats
	..
	// Depth / stencil
	case D3DFMT_D16_LOCKABLE:
	case D3DFMT_D16:
	case D3DFMT_D15S1:
	case D3DFMT_D24S8:
	case D3DFMT_D24X8:
	case D3DFMT_D24X4S4:
	case D3DFMT_D32:
		return 0;
		...
}

Do you know if the RTT path is much faster when MSAA is off compared to copyRects? Is it worth the extra complexity to support both?

@githubawn
Copy link
Author

githubawn commented Mar 2, 2026

Maybe we can accurately tell if MSAA was forced with some of the changes in #1073 like below, and use that in place of desc.MultiSampleType

To find this bug I tested a benchmark, but the test ran differently on different vendors, is this needed?
https://github.com/githubawn/GeneralsGameCode/tree/fix/nvidia-microwave-aa-clean/benchmark

Do you know if the RTT path is much faster when MSAA is off compared to copyRects? Is it worth the extra complexity to support both?

I tried benchmarking them, but the game lacks robust benchmarking tools. Using CapFrameX with a DX8 bridge means we’re mostly benchmarking the wrapper’s translation overhead; there’s too much fluctuation between runs to see a real difference between RTT and copyRects. It might be worth keeping both around in case we move to a native DX9 base where RTT isn't broken?

@stephanmeesters
Copy link

I tried benchmarking them, but the game lacks robust benchmarking tools. Using CapFrameX with a DX8 bridge means we’re mostly benchmarking the wrapper’s translation overhead; there’s too much fluctuation between runs to see a real difference between RTT and copyRects. It might be worth keeping both around in case we move to a native DX9 base where RTT isn't broken?

If you're up for it you could give #2202 a try; add a zone in the smudge code, then do a testrun on a replay or something that's kinda reproduceable. Tracy can compare histograms of zones between two different captures

@githubawn
Copy link
Author

githubawn commented Mar 2, 2026

I actually tried to set this up by bringing the Tracy profiling (#2202) into this branch to compare them.

Looking at the traces, the overall render zone copy is almost invisible below PassDefault in both pathways. The smudge rendering is incredibly fast either way. I added the detail zones and did a direct comparison between the RTT and CopyRects (smudge copy) pathways.

A true 1:1 benchmark would require reverting to a pre-safety commit, reapplying Tracy, and resolving build conflicts, which I've partially done but found overly time intensive.

Given the minimal frame time impact, I think CopyRects is sufficient here, but if you have ideas for a cleaner benchmark setup, I'm happy to iterate.

These are the results:

rtt=on2 rtt=off2 rtt=off rtt=on

@stephanmeesters
Copy link

Very cool! Seems that CopyRects is the way to go.

You can inspect the histogram of a zone by going to Statistics and then clicking on your zone which gives an image like this:
image

By pressing Ctrl + Numpad+ you can increase the max render FPS so that would maybe make the differences easier to see (if any)

@stephanmeesters
Copy link

stephanmeesters commented Mar 2, 2026

To find this bug I tested a benchmark, but the test ran differently on different vendors, is this needed? https://github.com/githubawn/GeneralsGameCode/tree/fix/nvidia-microwave-aa-clean/benchmark

I will take a look at this soon

Edit: D3DFormat doesn't appear to report accurately either, so it's not useful for detecting forced MSAA. Maybe you can use the surface mismatch that's throwing API errors as detector, (if it's important to detect this... maybe not)

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that it also fixes the Smudge on my machine (AMD Video Card). Very nice.

I did a very basic performance comparison between before and after fix with 35 Microwave tanks in an otherwise empty scene.

Before: ~230 FPS
After: ~230 FPS

I think we need to not worry about performance for now. The Microwave tank is also not a unit that is spammed much.

@xezon
Copy link

xezon commented Mar 5, 2026

Title needs updating because it is not Nvidia specific bug.

@xezon xezon added Bug Something is not working right, typically is user facing Critical Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour labels Mar 5, 2026
@xezon xezon added this to the Major bug fixes milestone Mar 5, 2026
@githubawn githubawn changed the title bugfix(rendering): Fix Microwave Heat Haze blackout on (Nvidia) forced AA bugfix(rendering): Fix Microwave Heat Haze blackout on forced AA Mar 5, 2026
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@github-actions
Copy link

⚠️ Title/Commit Validation Failed

Invalid commit messages:

  • Update Core/Libraries/Source/WWVegas/WWLib/WWCommon.h
  • implemented greptile feedback
  • implemented last feedback
  • implemented some feedback
  • Update Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp
  • Fix Microwave blackout on (Nvidia) forced AA
    PR titles and commit messages must follow conventional commits format:
type: Description
type(scope): Description

Allowed types: bugfix, build, chore, ci, docs, fix, feat, perf, refactor, revert, style, test, tweak, unify

See CONTRIBUTING.md for details.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me.

@Mauller
Copy link

Mauller commented Mar 12, 2026

So this could do with a followup to remove all of the RTT code from the smudge filter

@xezon xezon changed the title bugfix(rendering): Fix Microwave Heat Haze blackout on forced AA bugfix(smudge): Fix Microwave Heat Haze blackout on forced AA Mar 14, 2026
@xezon xezon merged commit 6da321a into TheSuperHackers:main Mar 14, 2026
23 checks passed
@githubawn githubawn deleted the fix/nvidia-microwave-aa branch March 14, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Critical Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants